package paim.wingchun.tarefa;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.Semaphore;


public abstract class Tarefa implements Runnable {

    Thread thread;

    protected boolean deveCancelar = false;

    protected boolean houveCancelamento = false;

    Date inicio;

    Date fim;

    boolean terminou = false;

    Long marcoTempo = null;

    protected boolean suspender = false;

    Date inicioSuspensao;

    long tempoSuspensao = 0;

    /**
     * Evento disparado ao final da tarefa (havendo ou não cancelamento e/ou erro). Use {@link #getHouveCancelamento()}
     * para verificar se houve cancelamento Use {@link #error} para verificar se houve erro. Metodo
     * {@link #inicializa(IEventoGenerico, IEventoProgresso, int, int)} já contempla este e outros
     * quesitos para uma inicializacao usual da tarefa. Eventos "terminar" sao acionados logo apos a execução do
     * {@link #finalizacao()} O evento terah como seu primeiro elemento do array a propria instancia da tarefa
     * cancelavel: {@link IEventoGenerico#onEvent(Object[])}
     */
    protected List<IEventoGenerico> eventosFimTarefa = new Vector<IEventoGenerico>(1);

    protected List<IEventoProgresso> eventosProgressoTarefa = new Vector<IEventoProgresso>(1);

    public Throwable error = null;

    private String descricao;

    protected String mensagem = "";

    private Semaphore semaforo = new Semaphore(1);

    // private final ObjetoMonitoracao obMonitorador;

    protected int multiploProgressoParaAtualizacao;

    protected int multipoProgressoFinoParaAtualizacao;

    private int progresso1Ant = 0;

    private int total1Ant = 0;

    public Tarefa(String nome) {
        this(true, nome);
    }

    public Tarefa(boolean monitorarTempo, String nome) {
        super();
        this.inicio = getAgora();
        try {
            /*Sinaliza "PARE" para quem está prestes a "parar" nesta sinaleira*/
            semaforo.acquire();
        }
        catch ( InterruptedException e ) {}

        this.descricao = nome;
        // if ( monitorarTempo ) {
        // this.obMonitorador = new ObjetoMonitoracao();
        // }
        // else {
        //
        // this.obMonitorador = null;
        // }
    }

    protected abstract void log(String passo, boolean mostrarEmProgresso);

    /**
     * Disparado logo no começo da execução da tarefa se retornar true, prossegue processamento, se false, a cancela (
     * {@link #setDeveCancelar(boolean)})
     */
    protected abstract boolean inicializacao() throws Exception;

    /**
     * Disparado no fim da tarefa(), havendo ou nao cancelamento ou erro. Use {@link #getHouveCancelamento()} para
     * verificar se houve cancelamento Use {@link #error} para verificar se houve erro. Os eventos de termino (
     * {@link #eventosFimTarefa} ) sao efetuados logo apos a chamada desta finalizacao.
     */
    protected abstract void finalizacao() throws Exception;

    /**
     * Onde deve-se fazer o processamento da tarefa que sobrescreve esta classe. DEVE olhar durante o processamento o
     * estado de {@link #deveCancelar}. Quando true, deve abortar o processamento, saindo do método...
     * 
     * @throws Exception
     */
    protected abstract void fazProcessamento() throws Exception;

    public final void run() {
        this.terminou = false;
        this.error = null;
        this.inicio = getAgora();
        try {
            ajustaProgresso(0, 0, 0, 0);

            try {
                if ( inicializacao() ) {
                    // if ( obMonitorador != null ) {
                    // obMonitorador.tempoInicial(getDescricao());
                    // }
                    try {
                        fazProcessamento();
                    }
                    finally {
                        // if ( obMonitorador != null ) {
                        // obMonitorador.tempoFinal(getDescricao());
                        // }
                    }
                }
                else {
                    setDeveCancelar(true);
                }
            }
            catch ( ExcecaoCancelamento ec ) {
                aoTerminarTarefa();
            }
            catch ( IllegalArgumentException e ) {
                this.error = e;
            }
            catch ( Throwable e ) {
                e.printStackTrace();
                this.error = e;
            }
            finally {
                aoTerminarTarefa();
            }
        }
        finally {
            semaforo.release(Integer.MAX_VALUE); // Sinaliza "SIGA" para quem está parado nesta sinaleira
        }
    }

    /** nova thread jah configurada com este {@link Runnable} e com a descricao desta tarefa. */
    public Thread newThread() {
        this.thread = new Thread(this, this.getDescricao());
        return this.thread;
    }

    public synchronized void start() {
        if ( this.thread == null )
            newThread();
        this.thread.start();
    }

    public Thread getThread() {
        return this.thread;
    }

    public void esperaFinalizacao() {
        try {
            /*Sinaliza que deve ficar parado nesta sinaleira até a mesma ficar "verde", que é quando a execução acaba...*/
            semaforo.acquire();
            /*Sinaliza que passou pela sinaleira e deve mantê-la "verde"*/
            semaforo.release();
        }
        catch ( InterruptedException e ) {
            System.out.println("erro inesperado...");
        }
    }

    public boolean isSuspenca() {
        return this.suspender;
    }

    public synchronized void suspender() {
        /*se ja esta suspenca cai fora*/
        if ( isSuspenca() )
            return;
        this.suspender = true;
        this.inicioSuspensao = getAgora();
    }

    public synchronized void resumir() {
        /*se nao esta suspenca cai fora*/
        if ( !isSuspenca() )
            return;
        this.suspender = false;

        this.tempoSuspensao += getAgora().getTime() - inicioSuspensao.getTime();
        this.inicioSuspensao = null;
        this.notify();
    }

    /** Marca o início de um processamento parcial. */
    public synchronized void setMarcoTempo() {
        this.marcoTempo = getAgora().getTime();
    }

    /** Tempo decorrido de processamento parcial, não incluindo o tempo de suspensão. */
    public synchronized long getMarcoDuracao() {
        if ( this.marcoTempo == null )
            return -1;
        return getAgora().getTime() - this.marcoTempo - tempoSuspensao;
    }

    /**
     * Ajusta o progresso desta tarefa, a qual pode ter um sub-progresso, indicado em progresso2 e totalProc2
     * 
     * @param progresso1
     * @param totalProc1
     * @param progresso2
     * @param totalProc2
     * @return
     */
    @Deprecated
    public boolean ajustaProgresso(int progresso1, int totalProc1, int progresso2, int totalProc2) {
        this.progresso1Ant = progresso1;
        this.total1Ant = totalProc1;
        if ( this.getDeveCancelar() ) {
            houveCancelamento = true;
            return false;
        }
        if ( suspender ) {
            synchronized ( this ) {
                while ( suspender )
                    try {
                        wait();
                    }
                    catch ( InterruptedException e ) {}
            }
        }
        if ( (eventosProgressoTarefa.size() > 0)
                && (((progresso2 <= 0) && ((progresso1 % this.multiploProgressoParaAtualizacao) == 0))
                        || ((progresso2 > 0) && ((progresso2 % this.multipoProgressoFinoParaAtualizacao) == 0))
                        || ((progresso2 <= 0) && ((progresso1 == totalProc1))) || ((progresso2 > 0) && ((progresso2 == totalProc2)))) ) {
            for ( IEventoProgresso prog : eventosProgressoTarefa ) {
                prog.onProgress(this, 0, totalProc1, progresso1, 0, totalProc2, progresso2, getDescricaoProc(
                        progresso1, totalProc1, progresso2, totalProc2));
            }
        }
        return true;
    }

    /**
     * Ajusta o progresso da sub-tarefa (progresso2), mantendo os ultimos valores de Progresso 1 (tarefa principal)
     * 
     * @param progresso1
     * @param totalProc1
     * @param progresso2
     * @param totalProc2
     * @return
     */
    @Deprecated
    public boolean ajustaProgresso2(int progresso2, int totalProc2) {
        return this.ajustaProgresso(this.progresso1Ant, this.total1Ant, progresso2, totalProc2);
    }

    @Deprecated
    protected String getDescricaoProc(int progresso1, int totalProc1, int progresso2, int totalProc2) {
        return ((totalProc1 <= 0) ? "início" : this.mensagem + progresso1 + " de " + totalProc1)
                + ((totalProc2 <= 0) ? "" : " (" + progresso2 + "/" + totalProc2 + ")");
    }

    private void aoTerminarTarefa() {
        this.terminou = true;
        this.fim = getAgora();
        this.houveCancelamento = this.deveCancelar;
        try {
            finalizacao();
            // JSLogger.loga(NivelLog.INFORMACAO, "Tarefa finalizada!");
        }
        catch ( Exception e ) {
            e.printStackTrace();
            if ( this.error == null )
                this.error = e;
        }
        if ( eventosFimTarefa.size() > 0 ) {
            for ( IEventoGenerico term : eventosFimTarefa ) {
                term.onEvent(new Object[] { this });
            }
        }
    }

    /**
     * Tempo decorrido de processamento, não incluindo o tempo de suspensão.
     * 
     * @return
     */
    public synchronized long getTempoProcesamento() {
        return (getInicio() == null) ? -1 : (getAgora().getTime() - getInicio().getTime() - tempoSuspensao);
    }

    public List<IEventoGenerico> getEventosFimTarefa() {
        return this.eventosFimTarefa;
    }

    public List<IEventoProgresso> getEventosProgressoTarefa() {
        return this.eventosProgressoTarefa;
    }

    public synchronized boolean getHouveCancelamento() {
        return houveCancelamento;
    }

    public synchronized void setDeveCancelar(boolean b) {
        deveCancelar = b;
    }

    /** @return true caso a tarefa foi SINALIZADA para cancelar. Isto NAO QUER dizer que a tarefa jah acabou. */
    public boolean getDeveCancelar() {
        return deveCancelar;
    }

    public final synchronized Date getInicio() {
        return inicio;
    }

    public final synchronized Date getFim() {
        return fim;
    }

    public final synchronized boolean isTerminou() {
        return terminou;
    }

    protected String getDescricao() {
        return descricao;
    }

    public String getMensagem() {
        return mensagem;
    }

    public void setMensagem(String mensagem) {
        this.mensagem = mensagem;
    }

    private Date getAgora() {
        return Calendar.getInstance().getTime();
    }

    public static final String tempoAsString(long tempoEmMs) {
        return (tempoEmMs < 0) ? "" : Long.toString((long) (tempoEmMs / 1000 / 60)) + "min "
                + Long.toString((long) (tempoEmMs / 1000) - ((long) (tempoEmMs / 1000 / 60)) * 60) + "s "
                + Long.toString(tempoEmMs % 1000) + "ms";
    }

    public static final String tempoAsStringSemMs(long tempoEmMs) {
        return (tempoEmMs < 0) ? "" : Long.toString((long) (tempoEmMs / 1000 / 60)) + "min "
                + Long.toString((long) (tempoEmMs / 1000) - ((long) (tempoEmMs / 1000 / 60)) * 60) + "s";
    }

}
